﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Xml;

namespace XmlDSigEx
{
    public class RsaAlgorithm:ISignatureAlgorithm
    {
        internal const string URI_RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
        internal const string URI_RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
        internal const string URI_RSA_SHA2_PREFIX = "http://www.w3.org/2001/04/xmldsig-more#rsa-";

        private RSACryptoServiceProvider _publicKey;

        public RSACryptoServiceProvider PublicKey
        {
            get { return _publicKey; }
            set { _publicKey = value; }
        }
        private RSACryptoServiceProvider _privateKey;

        public RSACryptoServiceProvider PrivateKey
        {
            get { return _privateKey; }
            set { _privateKey = value; }
        }

        public RsaAlgorithm(RSACryptoServiceProvider csp)
        {
            _privateKey = csp;
            _publicKey = csp;
        }

        public RsaAlgorithm(RSACryptoServiceProvider publicKey, RSACryptoServiceProvider privateKey)
        {
            _privateKey = privateKey;
            _publicKey = publicKey;
        }

        internal RsaAlgorithm(XmlElement signatureElement)
        {
            XmlElement keyInfoElement = signatureElement.SelectSingleNode("child::*[local-name() = 'KeyInfo']") as XmlElement;
            foreach (XmlElement keyInfoChild in keyInfoElement.ChildNodes)
            {
                if (keyInfoChild.LocalName == "KeyValue" && keyInfoChild.FirstChild.LocalName == "RSAKeyValue")
                {
                    ProcessKeyValue(keyInfoChild);
                    return;
                }
            }
        }

        public byte[] SignData(System.IO.Stream data, string signatureMethod)
        {
            string digestAlgorithm = GetAlgorithmFromUrl(signatureMethod);
            return _privateKey.SignData(data, digestAlgorithm);
        }

        public bool VerifySignature(System.IO.Stream data, byte[] signature, string signatureMethod)
        {
            if (_publicKey == null)
                throw new InvalidOperationException("No key was specified and none could be generated from the signature.");
            string digestAlgorithm = GetAlgorithmFromUrl(signatureMethod);
            byte[] dataArray = new byte[data.Length];
            data.Read(dataArray, 0, dataArray.Length);
            return _publicKey.VerifyData(dataArray, digestAlgorithm, signature);
        }

        public virtual void WriteKeyInfo(System.Xml.XmlWriter writer)
        {
            if (WriteRsaKeyValue)
            {
                RsaKeyValueWriter rsaWriter = new RsaKeyValueWriter(_publicKey);
                rsaWriter.WriteXml(writer);
            }
        }

        public virtual bool WillWriteKeyInfo
        {
            get
            {
                return WriteRsaKeyValue;
            }
        }

        public bool WriteRsaKeyValue { get; set; }

        private void ProcessKeyValue(XmlElement keyValue)
        {
            _publicKey = new RSACryptoServiceProvider();
            _publicKey.PersistKeyInCsp = false;
            _publicKey.FromXmlString(keyValue.OuterXml);
        }

        private string GetAlgorithmFromUrl(string signatureMethod)
        {
            if (signatureMethod == URI_RSA_SHA1)
                return "SHA1";

            if (!signatureMethod.StartsWith(URI_RSA_SHA2_PREFIX))
                throw new ArgumentException(string.Format("An unknown algorithm: {0} was used.", signatureMethod), "signatureMethod");

            switch (signatureMethod.Replace(URI_RSA_SHA2_PREFIX, ""))
            {
                case "sha256":
                    return "SHA256";
                case "sha512":
                    return "SHA512";

                default:
                    throw new ArgumentException(string.Format("An unknown algorithm: {0} was used.", signatureMethod), "signatureMethod");
            }

        }
    }
}
